package com.chenju.invokechainservice.service.impl;

import com.chenju.invokechainservice.adapter.MethodVisitorAdapter;
import com.chenju.invokechainservice.common.enums.ChangeType;
import com.chenju.invokechainservice.common.exceptions.FunnyException;
import com.chenju.invokechainservice.common.exceptions.FunnyNullException;
import com.chenju.invokechainservice.common.exceptions.FunnyValidationException;
import com.chenju.invokechainservice.component.CodeExecutor;
import com.chenju.invokechainservice.component.BuildComponent;
import com.chenju.invokechainservice.domain.DiffMethodDetailEntity;
import com.chenju.invokechainservice.domain.DiffMethodEntity;
import com.chenju.invokechainservice.service.JavaParserService;
import com.chenju.invokechainservice.utils.FunnyFileUtils;
import com.chenju.invokechainservice.utils.FunnyStringUtils;
import com.chenju.invokechainservice.utils.FunnyXmlUtils;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ClassLoaderTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

import static com.chenju.invokechainservice.utils.GitRepositoryComparator.generateDiffFileMap;

@Service
public class JavaParserServiceImplement implements JavaParserService {
    @Autowired
    CodeExecutor codeExecutor;

    @Autowired
    private BuildComponent build;

    @Override
    public CompilationUnit javaParser(String filePath, JavaSymbolSolver symbolSolver) {
        try {
            if (symbolSolver != null){
                StaticJavaParser.getParserConfiguration().setSymbolResolver(symbolSolver);
            }
            CompilationUnit cu = StaticJavaParser.parse(new FileInputStream(filePath));

            // 删除注释
            List<Comment> comments = cu.getAllContainedComments();
            comments.forEach(Node::removeForced);
            return cu;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<DiffMethodEntity> compareCompilationUnit(String rootPath, Map<String, ChangeType> fileChangeTypeMap) {
        if (fileChangeTypeMap == null) {
            throw new FunnyNullException("传入的FileChangeMap为空，请检查");
        }
        if (rootPath == null || "".equals(rootPath)){
            throw new FunnyNullException("传入得rootPath为空，请检查");
        }
        List<DiffMethodEntity> diffs = new ArrayList<>();
        for (Map.Entry<String, ChangeType> entry : fileChangeTypeMap.entrySet()) {
            String filePath = entry.getKey();
            if (!filePath.endsWith(".java")){
                continue;
            }
            ChangeType changeType = entry.getValue();

            // 设置原始分支的symbolSolver
            JavaSymbolSolver oldSymbolSolver = setJavaSymbolSolver(rootPath, "master");
            // 设置新分支的symbolSolver
            JavaSymbolSolver newSymbolSolver = setJavaSymbolSolver(rootPath, "feature");

            if (changeType == ChangeType.MODIFY) {
                String oldAbsoluteFilePath = rootPath + "\\master\\" + filePath;
                String newAbsoluteFilePath = rootPath + "\\feature\\" + filePath;
                CompilationUnit oldCu = javaParser(oldAbsoluteFilePath, oldSymbolSolver);
                CompilationUnit newCu = javaParser(newAbsoluteFilePath, newSymbolSolver);

                diffs.addAll(compareMethod(oldCu, newCu));
            } else if (changeType == ChangeType.ADD) {
                MethodVisitorAdapter newMethodVisitorAdapter = new MethodVisitorAdapter();
                String newAbsoluteFilePath = rootPath + "\\feature\\" + filePath;
                CompilationUnit newCu = javaParser(newAbsoluteFilePath, newSymbolSolver);

                newMethodVisitorAdapter.visit(newCu, "");
                // 所有的方法都是新增的
                List<MethodDeclaration> newMethods = newMethodVisitorAdapter.methods;
                newMethods.forEach(newMethod -> diffs.add(new DiffMethodEntity(null, newMethod, ChangeType.ADD)));
            } else if (changeType == ChangeType.DELETE) {
                MethodVisitorAdapter oldMethodVisitorAdapter = new MethodVisitorAdapter();
                String oldAbsoluteFilePath = rootPath + "\\feature\\" + filePath;
                CompilationUnit oldCu = javaParser(oldAbsoluteFilePath, oldSymbolSolver);

                oldMethodVisitorAdapter.visit(oldCu, "");
                // 所有的方法都是删除的
                List<MethodDeclaration> oldMethods = oldMethodVisitorAdapter.methods;
                oldMethods.forEach(oldMethod -> diffs.add(new DiffMethodEntity(oldMethod, null, ChangeType.DELETE)));
            }
        }
        return diffs;
    }

    @Override
    public List<DiffMethodEntity> parseProjectDiffMethod(String rootPath) {
        String oldCodePath = FunnyStringUtils.joinPath(rootPath, "master");
        String newCodePath = FunnyStringUtils.joinPath(rootPath, "feature");
        String oldRepositoryPath = FunnyStringUtils.joinPath(oldCodePath, ".git");
        String newRepositoryPath = FunnyStringUtils.joinPath(newCodePath, ".git");
        Map<String, ChangeType> diffFileMap = generateDiffFileMap(oldRepositoryPath, newRepositoryPath);
        return compareCompilationUnit(rootPath, diffFileMap);
    }

    @Override
    public List<DiffMethodEntity> compareMethod(CompilationUnit oldCu, CompilationUnit newCu) {
        List<DiffMethodEntity> diffs = new ArrayList<>();
        MethodVisitorAdapter oldMethodVisitorAdapter = new MethodVisitorAdapter();
        MethodVisitorAdapter newMethodVisitorAdapter = new MethodVisitorAdapter();

        oldMethodVisitorAdapter.visit(oldCu, "");
        newMethodVisitorAdapter.visit(newCu, "");

        List<MethodDeclaration> oldMethods = oldMethodVisitorAdapter.methods;
        List<MethodDeclaration> newMethods = newMethodVisitorAdapter.methods;

        Iterator<MethodDeclaration> oldMethodIterator = oldMethods.iterator();
        while (oldMethodIterator.hasNext()) {
            MethodDeclaration oldMethod = oldMethodIterator.next();
            String oldMethodSignature = oldMethod.getSignature().asString();
            String oldMethodString = oldMethod.toString();

            Iterator<MethodDeclaration> newMethodIterator = newMethods.iterator();
            while (newMethodIterator.hasNext()) {
                MethodDeclaration newMethod = newMethodIterator.next();
                if (oldMethodSignature.equals(newMethod.getSignature().asString())) {
                    if (!oldMethodString.equals(newMethod.toString())) {
                        DiffMethodEntity methodDiff = new DiffMethodEntity(oldMethod, newMethod, ChangeType.MODIFY);
                        DiffMethodDetailEntity detail = compareMethodDiffDetail(oldMethod, newMethod);
                        methodDiff.setDetail(detail);
                        diffs.add(methodDiff);
                    }
                    oldMethodIterator.remove();
                    newMethodIterator.remove();
                }
            }
        }

        oldMethods.forEach(oldMethod -> diffs.add(new DiffMethodEntity(oldMethod, null, ChangeType.DELETE)));
        newMethods.forEach(newMethod -> diffs.add(new DiffMethodEntity(null, newMethod, ChangeType.ADD)));

//        diffs.forEach(diffMethodEntity -> {
//            System.out.println(diffMethodEntity.getNewMethod().getSignature());
//            System.out.println(diffMethodEntity.getOldMethod().getSignature());
//            System.out.println(diffMethodEntity.getChangeType());
//        });

        return diffs;
    }

    @Override
    public DiffMethodDetailEntity compareMethodDiffDetail(MethodDeclaration oldMethod, MethodDeclaration newMethod) {
        DiffMethodDetailEntity detail = new DiffMethodDetailEntity();

        // 对比annotations
        List<String> oldAnnotaions = findStringAnnotations(oldMethod.getAnnotations());
        List<String> newAnnotaions = findStringAnnotations(newMethod.getAnnotations());
        if (!new HashSet<>(oldAnnotaions).equals(new HashSet<>(newAnnotaions))) {
            detail.setOldAnnotations(oldAnnotaions);
            detail.setNewAnnotations(newAnnotaions);
        }

        // 对比Modifiers
        List<String> oldModifiers = findStringModifiers(oldMethod.getModifiers());
        List<String> newModifiers = findStringModifiers(newMethod.getModifiers());
        if (!new HashSet<>(oldModifiers).equals(new HashSet<>(newModifiers))){
            detail.setOldModifiers(oldModifiers);
            detail.setNewModifiers(newModifiers);
        }

        // 对比returnType
        String oldReturnType = oldMethod.getTypeAsString();
        String newReturnType = newMethod.getTypeAsString();
        if (!oldReturnType.equals(newReturnType)){
            detail.setOldReturnType(oldReturnType);
            detail.setNewReturnType(newReturnType);
        }

        // 对比parameterNames
        List<String> oldParameterNames = findStringParameters(oldMethod.getParameters());
        List<String> newParameterNames = findStringParameters(newMethod.getParameters());
        if (!new HashSet<>(oldParameterNames).equals(new HashSet<>(newParameterNames))){
            detail.setOldParameters(oldParameterNames);
            detail.setNewParameters(newParameterNames);
        }

        // 对比throw Exceptions
        List<String> oldThrowExceptions = findStringThrowExceptions(oldMethod.getThrownExceptions());
        List<String> newThrowExceptions = findStringThrowExceptions(newMethod.getThrownExceptions());
        if (!new HashSet<>(oldThrowExceptions).equals(new HashSet<>(newThrowExceptions))){
            detail.setOldThrowExceptions(oldThrowExceptions);
            detail.setNewThrowExceptions(newThrowExceptions);
        }

        // 对比body
        BlockStmt oldBody = oldMethod.getBody().isPresent() ? oldMethod.getBody().get() : null;
        BlockStmt newBody = newMethod.getBody().isPresent() ? newMethod.getBody().get() : null;
        if (oldBody == null && newBody != null){
            detail.setNewBody(newBody.toString());
        }
        if (oldBody != null && newBody == null) {
            detail.setOldBody(oldBody.toString());
        }
        if (oldBody != null && newBody != null) {
            // 直接比较字符串信息
            boolean equals = oldBody.toString().equals(newBody.toString());
            if (!equals){
                detail.setOldBody(oldBody.toString());
                detail.setNewBody(newBody.toString());
            }
        }
        return detail;
    }

    @Override
    public List<String> findStringAnnotations(NodeList<AnnotationExpr> annotationExprs) {
        if (CollectionUtils.isEmpty(annotationExprs)){
            return new ArrayList<>();
        }
        List<String> annotations = new ArrayList<>();
        annotationExprs.forEach(annotationExpr -> {
            annotations.add(annotationExpr.resolve().getQualifiedName());
        });
        return annotations;
    }

    @Override
    public List<String> findStringModifiers(NodeList<Modifier> modifiers) {
        if (CollectionUtils.isEmpty(modifiers)){
            return new ArrayList<>();
        }
        List<String> strModifiers = new ArrayList<>();
        modifiers.forEach(modifier -> {
            strModifiers.add(modifier.getKeyword().asString());
        });
        return strModifiers;
    }

    @Override
    public List<String> findStringParameters(NodeList<Parameter> parameters) {
        if (CollectionUtils.isEmpty(parameters)){
            return new ArrayList<>();
        }
        List<String> strParameters = new ArrayList<>();
        parameters.forEach(parameter -> {
            strParameters.add(parameter.getNameAsString());
        });
        return strParameters;
    }

    @Override
    public List<String> findStringThrowExceptions(NodeList<ReferenceType> throwExceptions) {
        if (CollectionUtils.isEmpty(throwExceptions)){
            return new ArrayList<>();
        }
        List<String> strThrowExceptions = new ArrayList<>();
        throwExceptions.forEach(throwException -> {
            strThrowExceptions.add(throwException.asString());
        });
        return strThrowExceptions;
    }

    @Override
    public JavaSymbolSolver setJavaSymbolSolver(String rootPath, String release) {
        // 设置原始分支的symbolSolver
        CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
        combinedTypeSolver.add(new ReflectionTypeSolver());
        // Jar文件暂时先不添加
        // oldCombinedTypeSolver.add(new JarTypeSolver(new File("D:\\Project\\springboot-easytest\\invokechain-service\\target\\invokechain-service-0.0.1-SNAPSHOT.jar")));
        try {
            // 区分gradle项目和maven项目
            List<String> modulePaths = FunnyFileUtils.findModulePaths(rootPath, build.getBuildTool());
            for (String modulePath : modulePaths) {
                String classesFilePath = FunnyStringUtils.getClassesFilePath(build.getClassesDir());
                String classesPath = FunnyStringUtils.joinPath(modulePath, classesFilePath);
                URL[] urls = new URL[]{new File(classesPath).toURI().toURL()};
                URLClassLoader loader = new URLClassLoader(urls);
                ClassLoaderTypeSolver classLoaderTypeSolver = new ClassLoaderTypeSolver(loader);
                combinedTypeSolver.add(classLoaderTypeSolver);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return new JavaSymbolSolver(combinedTypeSolver);
    }
}
